class BlackDeathHeroPawn extends BlackDeathPawn
     placeable;


var() float UseableCheckRadius;

var name lastStateBeforeStagger; //where were we when I was staggered again?

//Weapon Animation
var AnimNodeBlendList HAttackType;
var AnimNodeBlendList VAttackType;
var AnimNodeBlend SwingVar;
var AnimNodeBlend Swing_Blend;
var AnimNodeBlendByProperty ShoveNode;

//Damage Animation
var AnimNodeBlendPerBone DamageNode;
var AnimNodeSequence Hit;

var AnimNodeBlendList WeaponStateNode; //Node that blends equips
var AnimNodeBlendList IdleStateNode;

//Bandage Animation
var AnimNodeSequence Bandage;


var int ShoveRange; //Maximum range of a shove
var int ShoveDamage; //how much damage shove does
var int ShoveMomentum; //mesure of how velocity to add upon Shoving a target

var float BurnLength;//How long Vincient will burn before automatically extinguishing

/** var to record the last weapon held so we can switch back when going back to attack */
var BlackDeathWeapon LastHeldWeapon;

var bool bEndAttackingState; //flag that is set if the pawn is leaving the attack state that notifies the pawn to transition


var() Name SkelControlLookAtName;
var SkelControlLookAt SkelControlLookAt;

var BlackDeathPawn Zombie;
var vector snapOnDistance;
var int LookAtCount;
var bool POTEnabled;
var vector POTLocation;

/** Running Varibles */
var() float DefaultRunSpeed; //How fast the players default speed is. Multipliers are used to alter speed based on this base.
var float CurrentBaseSpeed; //How fast the current base speed is, used to reset speed after apllying multipliers
var name currentState; //The state the player was in before running


/** Stamina Variables **/
//Determines the maximum Stamina that player can have
var() int StaminaMax;

var int Stamina;//current stamina
var int StaminaCooldownCeiling; //The percentage of max stamina at which forced cooldown ends
var bool bUsingStamina; //are we currently applying stamina
var bool bForceFullCooldown;//Do we have to wiat for full colldown to run?

var float damageModifier;
var float momentumModifier;


//Stamina Constants
var const float IDLE_STAMINA_TAX;
var const float RUNNING_STAMINA_TAX;
var const float STAMINA_DAMAGE_MODIFIER;
var const float STAMINA_MOMENTUM_MODIFIER;
var const float SHOVING_STAMINA_TAX;

var const float MAX_TIME_BETWEEN_FULL_HEARTBEATS;
var const float	MAX_TIME_BETWEEN_SINGLE_HEARTBEATS;

var SoundCue BandageCue;
Var AudioComponent BandageSound;


var SoundCue Heartbeat1;
var SoundCue Heartbeat2;
var float timeBetweenFullBeats;
var float timeBetweenSingleBeats;
var int heartrate; //must be int from 0 to 4

var AudioComponent PantingSound;
var SoundCue PANTING_SOUND_CUE;

//Attack Combo system
var const float COMBO_DAMAGE_MODIFIER;
var const float COMBO_WINDOW;
var int comboMoveToExecute; //0 if no combo, 1 for first combo, 2 for final combo
var bool bEarnedCombo;

//Keep track of if weapon is firing, make sure it can't be fired again.
var bool bWeaponFiring;
var bool bRecoil;

//TODO: Apply running stamina tax when actually running
//END Stamina variables

//Hold enumeration for what health state we're in
enum EHealthState
{
    EHS_Excellent,
    EHS_Fair,
    EHS_Bad,
    EHS_Dead
};

var int ExcellentMinHealth;//Minimum Health for the excellent Health state
var int FairMinHealth;//Minimum Health for the Good health state
//The current health level of the player
var EHealthState CurrentHealthState;
var int numberOfBandages;

var AnimNodeBlendList HealthNode;

var int LocalHealthMax;//Max health for current health level

var int CameraHealth; //changes the player's health by smaller ammounts for camera tilt, for whatever reason float math isn't working correctly so i am using an int x larger
var int CHMultiplier;
var int CameraDamage;
var int CameraDamageSpeed;

var bool FinisherUsed;

//Scalar for how much the camera should tilt with damage.
var float TiltDamageScale;
var float TiltDamagePeriod;

//Attachments for SkeletalMesh
var() SkeletalMeshComponent Satchel;
//END VARIBLES


simulated function PostBeginPlay()
{
    super.PostBeginPlay();
    Stamina = StaminaMax;
    CheckHealthState();
	CameraHealth = CHMultiplier * HealthMax;
	if (Mesh != none && Satchel != none)
		{
		Mesh.AttachComponentToSocket(Satchel, 'Satchel');
		}
	PantingSound = new(self) class'AudioComponent';
	AttachComponent(PantingSound);
	PantingSound.SoundCue = PANTING_SOUND_CUE;
	BandageSound = new(self) class'AudioComponent';
	AttachComponent(BandageSound);
	BandageSound.SoundCue = BandageCue;
	
    //gotostate('default');
}


/*This Function is called after the AnimTre is initialized.
*Any nodes that we want to control code, we should look up here.
*/
    simulated event PostInitAnimTree(SkeletalMeshComponent SkelComp)
    {
        super.PostInitAnimTree(SkelComp);
        if (SkelComp == Mesh)
        {
            ShoveNode = AnimNodeBlendByProperty(Mesh.FindAnimNode('ShoveNode'));
            HealthNode = AnimNodeBlendList(Mesh.FindAnimNode('HeathState'));
            HAttackType = AnimNodeBlendList(Mesh.FindAnimNode('Horizontal'));
            VAttackType = AnimNodeBlendList(Mesh.FindAnimNode('Vertical'));
            SwingVar = AnimNodeBlend(Mesh.FindAnimNode('SwingVar'));
            AimNode = AnimNodeAimOffset( mesh.FindAnimNode('AimNode') );
            WeaponStateNode = AnimNodeBlendList( mesh.FindAnimNode('Weap_State'));
			IdleStateNode = AnimNodeBlendList( mesh.FindAnimNode('Idle_States'));
			Swing_Blend = AnimNodeBlend (mesh.FindAnimNode('Swing_Blend'));
			DamageNode = AnimNodeBlendPerBone (mesh.FindAnimNode('DamageNode'));
			Hit = AnimNodeSequence (mesh.FindAnimNode('Hit'));
			Bandage = AnimNodeSequence (mesh.FindAnimNode('Bandage'));
			SkelControlLookAt = SkelControlLookAt(Mesh.FindSkelControl('HeadLook'));
			SkelControlLookAt.MaxAngle = 180;
			SkelControlLookAt.TargetLocationInterpSpeed = 1;
			//SkelControlLookAt.OuterMaxAngle = 180;
			SkelControlLookAt.bEnableLimit = true;
			//SkelControlLookAt.bDisableBeyondLimit = true;
			SkelControlLookAt.bAllowRotationY = false;
			//SkelControlLookAt.bAllowRotationX = false;
        }
}

/** Change the type of weapon animation we are playing. */
/*simulated function SetWeapAnimType(EWeapAnimType AnimType)
{
    if (AimNode != None)
    {
        switch(AnimType)
        {
            case EWAT_Default:
                AimNode.SetActiveProfileByName('Default');
                break;
            case EWAT_Pistol:
                AimNode.SetActiveProfileByName('SinglePistol');
                break;
            case EWAT_DualPistols:
                AimNode.SetActiveProfileByName('DualPistols');
                break;
            case EWAT_ShoulderRocket:
                AimNode.SetActiveProfileByName('ShoulderRocket');
                break;
            case EWAT_Stinger:
                AimNode.SetActiveProfileByName('Stinger');
                break;
        }
    }
}*/

function AddDefaultInventory()
{
    local int i;
    for (i=0; i < InitialInventory.Length; i++)
    {
        CreateInventory(InitialInventory[i]);
    }
}

//----------------------Health-------------------------
simulated function SetHealthAnimType(EHealthState AnimType)
{
    if (HealthNode != None)
    {
        switch(AnimType)
        {
            case EHS_Excellent:
                HealthNode.SetActiveChild(0, 0.0);
                break;
            case EHS_Fair:
                HealthNode.SetActiveChild(1, 0.0);
                break;
            case EHS_Bad:
                HealthNode.SetActiveChild(2, 0.0);
                break;
            case EHS_Dead:
                break;
        }
    }
}

event bool HealDamage(int Amount, Controller Healer, class<DamageType> DamageType)
{
	// not if already dead or already at full
	if (Health > 0 && Health < HealthMax)
	{
		Health = Min(HealthMax, Health + Amount);
		CheckHealthState();
		if(health < LocalHealthMax)
		{
			ClearTimer('StartRegenHealth');//Turn off any current regen so we can restart
			ClearTimer('IncrementHealth');
			SetTimer(2.0, false, 'StartRegenHealth');
		}
		return true;
	}
	else
	{
		return false;
	}
}


event TakeDamage(int Damage, Controller InstigatedBy, vector HitLocation, vector Momentum, class<DamageType> DamageType, optional TraceHitInfo HitInfo, optional Actor DamageCauser)
{
    Super.TakeDamage(Damage, InstigatedBy, HitLocation, Momentum, DamageType, HitInfo, DamageCauser);
	//WorldInfo.Game.Broadcast(None, "Damage Is: "@Damage);  //Shows Damage taken
	if(!bWeaponFiring)
	{//Don't play damage animation if attack animation is playing -- game currently doesn't handle interupting that animation well
		DamageNode.Setblendtarget(1, 0.2); //Blending in Damage Animation
		Hit.Playanim(false,1.0,0); //Playing Damage Animation
	}
	//camera Damage
	//CameraHealth -= ( CHMultiplier * Damage);
	ClearTimer('DecrimentCameraHealth');
	CameraDamage = ( CHMultiplier * Damage);
	SetTimer( ( 1.0 / CameraDamageSpeed ) / CHMultiplier, true, 'DecrimentCameraHealth');
	
    CheckHealthState();
    ClearTimer('StartRegenHealth');//Turn off any current regen so we can restart
    ClearTimer('IncrementHealth');
    if(health > 0)
    {
        SetTimer(2.0, false, 'StartRegenHealth');
    }
}

simulated function EndPlayDamage()
{
	DamageNode.SetBlendTarget(0,0.5);
}

simulated function DecrimentCameraHealth()
{
	if( CameraDamage > 0 )
	{
		CameraDamage -= 1;
        CameraHealth  -= 1;
    }
    else
    {
		//WorldInfo.Game.Broadcast(None, 'Camera Damage Taken '@CameraHealth);
        ClearTimer('DecrimentCameraHealth');
    }
}

simulated function StartRegenHealth()//Sets health to recover itself over time
{
    if(Health < LocalHealthMax)
    {
        Health += 1;
        SetTimer(0.5, true, 'IncrementHealth');
		SetTimer(0.5 / CHMultiplier, true, 'IncrementCameraHealth');
    }
}

simulated function IncrementHealth()
{
    if(Health < LocalHealthMax)
    {
        Health += 1;
        //WorldInfo.Game.Broadcast(None, "health regened to "@Health);
    }
    else
    {
        ClearTimer('IncrementHealth');
        //WorldInfo.Game.Broadcast(None, "Health is full at "@Health);
    }
}

simulated function IncrementCameraHealth()
{
    if( CameraHealth < LocalHealthMax*CHMultiplier)
    {
        CameraHealth += 1;
    }
    else
    {
        ClearTimer('IncrementCameraHealth');
    }
}

function CheckHealthState()
{
    switch(CurrentHealthState)
    {
        //find the current health state based on the enums and check to see if we dropped below min health
        case EHS_Excellent:
            if(Health < ExcellentMinHealth)
            {
                setHealthState(EHS_Fair);
            }
            else
            {
                SetBaseSpeed(DefaultRunSpeed);
                break;
            }
        case EHS_Fair:
            if(health < FairMinHealth)
            {
                setHealthState(EHS_Bad);
            }
            else
            {
                SetBaseSpeed(DefaultRunSpeed*0.8);
                break;
            }
        case EHS_Bad:
            if(health <= 0)
            {
                setHealthState(EHS_dead);
            }
            else
            {
                SetBaseSpeed(DefaultRunSpeed*0.6);
                break;
            }
        case EHS_Dead:
            break;
    }
}

simulated function setHealthState(EHealthState newState)
{
	switch(newState)
	{
		case EHS_Excellent: LocalHealthMax = HealthMax; break;
		case EHS_Fair: LocalHealthMax = ExcellentMinHealth -1; break;
		case EHS_Bad: LocalHealthMax = FairMinHealth-1; break;
		case EHS_Dead: LocalHealthMax = 0; break;
	}
	CurrentHealthState = newState;
	SetHealthAnimType(newState);
}

exec simulated function ResetHealth()
{
    setHealthState(EHS_Excellent);
    Health = HealthMax;
    CheckHealthState();
}

/*--test--*/exec simulated function DamageHealth()
{
    setHealthState(EHS_Bad);
    Health = LocalHealthMax;
	CameraHealth = Health * CHMultiplier;
    CheckHealthState();
    //WorldInfo.Game.Broadcast(None, 'Health is damaged to '@Health);
}

//trigger this function as an event
exec simulated function AddBandage()
{
	//****  uncomment lines below If we want to put a limit on number of bandages in inventory ****
	//if (numberOfBandages < 10) // 10 is the maximum number of bandages able to be carried in inventory
	//{
		numberOfBandages++; //add a bandage to inventory
		WorldInfo.Game.Broadcast(None, "You now have" @numberOfBandages $ " Bandages.");
	//}
}

//BIND this function to a button
exec simulated function BandageHealth()
{
	gotostate('Bandaging');
}
//Toggle function if player lets go of bandage button pre-maturely

state Bandaging
{
    event BeginState(Name PreviousStateName)
    {
	if ( numberOfBandages > 0 )
	{
		if (Health < HealthMax)
		{
			changestate(6, 0.2);
			Bandage.playanim(false,1.0,0.0);
			bJumpCapable = false;
			PlayerController(Controller).IgnoreMoveInput(true);
			currentState = 'Bandaging';
			
		}
		else
		{
		
		WorldInfo.Game.Broadcast(none, "You are already fully healed");
		gotostate('default');
		
		}
	}
	else
	{
	WorldInfo.Game.Broadcast(none, "You do not have any Bandages");
	gotostate('default');
	}
	
    }
    
   exec function StopBandage()
    {
        goToState('Default');
    }
    
    event EndState(Name NextStateName)
    {
        ResetSpeed();
        PlayerController(Controller).IgnoreMoveInput(false);
        bJumpCapable = true;
        changestate(0, 0.2);
    }
}

//Function will activate only if the player holds down the Bandage button until the notify is called
simulated function RestoreHealth()
{
WorldInfo.Game.Broadcast(None,"You now have" @numberOfBandages $ " Bandages.");
	if  ( numberOfBandages > 0 ) 
	{	
		if (Health < HealthMax){
			numberOfBandages--; //subtract bandage from inventory
			if (CurrentHealthState == EHS_Fair){
				setHealthState(EHS_Excellent);
				Health = LocalHealthMax;
				CameraHealth = LocalHealthMax*CHMultiplier;
				CheckHealthState();
				WorldInfo.Game.Broadcast(None, "Health is bandaged to "@Health);
				WorldInfo.Game.Broadcast(None, "Your health is now Excellent");
			}
			else if (CurrentHealthState == EHS_Bad){
				setHealthState(EHS_Fair);
				Health = LocalHealthMax;
				CameraHealth = LocalHealthMax*CHMultiplier;
				CheckHealthState();
				WorldInfo.Game.Broadcast(None, "Health is bandaged to "@Health);
				WorldInfo.Game.Broadcast(None, "Your health is now Fair");
			}
		}
		BandageSound.Play();
	}
} 

simulated function Ignite()
{
    if(!bOnFire)
    {
        super.Ignite();
        SetTimer(BurnLength, false, 'Extinguish');
    }
}

simulated function Extinguish()
{
    if(bOnFire)
    {
        Super.Extinguish();
        ClearTimer('Extinguish');
    }
}

function SetBaseSpeed(float BaseSpeed)
{
	if(!bUsingStamina)
	{//Nothing can change the player's speed when he is spending stamina
		CurrentBaseSpeed = BaseSpeed;
		AirSpeed=BaseSpeed;
		GroundSpeed=BaseSpeed;
	}
}

function ApplySpeedMultiplier(float Amount)
{
    AirSpeed = CurrentBaseSpeed*Amount;
    GroundSpeed = CurrentBaseSpeed*Amount;
}

function ResetSpeed()
{
    AirSpeed=CurrentBaseSpeed;
    GroundSpeed=CurrentBaseSpeed;
}

simulated function float CalcDamageTilt()//Sets how much the camera should tilt based on the player's health.
{
	local float tilt;
	local float HealthRatio;
	if(LocalHealthMax <= 0)
    {
        return 0.0f;
    }	
	//WorldInfo.Game.Broadcast(None, "tilt is"@TiltDamageFrameCount );
	HealthRatio = CalcCameraRegenHealthRatio();
	tilt = HealthRatio * 10430.37826809827*TiltDamageScale*sin( WorldInfo.TimeSeconds * TiltDamagePeriod );

    return tilt;
}
function float CalcCameraRegenHealthRatio()
{	//calculates ratio of health to LocalHealth
	return Abs( CameraHealth - ( LocalHealthMax * CHMultiplier) ) / ( LocalHealthMax * CHMultiplier );
}
function float CalcCameraHealthRatio()
{	//calculates ratio of health to max health
	return Abs( CameraHealth - ( HealthMax * CHMultiplier) ) / ( HealthMax * CHMultiplier );
}
/*-----------------------------Default state-----------------------------------*/
state Default
{
    event BeginState(Name PreviousStateName)
    {
        `log("Entering normal state");
        if(BlackDeathCamera(PlayerController(Controller).PlayerCamera) != none)
        {
            PlayerController(Controller).SetCameraMode('FollowCam');
        }
        changeState(0, 0.2);
		currentState = 'Default';
    }
}


simulated event Destroyed()
{
  Super.Destroyed();

  SkelControlLookAt = None;
}


simulated event Tick(float DeltaTime)
{
  Super.Tick(DeltaTime);

	
	//if Point of interest location is enabled look toward it.
	if (POTEnabled)
	{
		if ( SkelControlLookAt.CanLookAtPoint(POTLocation) )
		{
			SkelControlLookAt.bDisableBeyondLimit = false;
			SkelControlLookAt.TargetLocation = POTLocation;
		}
		else
			SkelControlLookAt.bDisableBeyondLimit = true;
	}
	//search for nearest Zombie
	else
	{
		ForEach OverLappingActors(class'BlackDeathPawn', Zombie, 300)
		{
			if ( Zombie != none ) 
			{	if ( SkelControlLookAt.CanLookAtPoint(Zombie.Location) )
				{
					SkelControlLookAt.bDisableBeyondLimit = false;
					SkelControlLookAt.TargetLocation = Zombie.Location;
				}
				else
					SkelControlLookAt.bDisableBeyondLimit = true;
			}
			break;
		}
	}
	
}

//exec function that chooses the actor the player should look at
exec simulated function LookOverTo(Actor PointOfInterest)
{
	POTEnabled = true;
	WorldInfo.Game.Broadcast(None, "POTCordinateX =  "@PointOfInterest.Location.x);
	WorldInfo.Game.Broadcast(None, "POTCordinateY =  "@PointOfInterest.Location.y);
	WorldInfo.Game.Broadcast(None, "POTCordinateZ =  "@PointOfInterest.Location.z);
	POTLocation = PointOfInterest.Location;
}

//disable Point Of Interest Mode
exec simulated function DisablePOT()
{
	POTEnabled = false;
}
/*----------------- running functions and controls------------------ */

//Run is now actually just "stamina use" since it can be used for more things than just running.
simulated function StartRun()
{
    if(hasStamina())
    {
        engageStamina();
    }
}

simulated function EndRun()
{
	//WorldInfo.Game.Broadcast(none, "Stamina at: "@Stamina);
	if( Stamina <= 0 )
		ForceStaminaCoolDown();
	else
		disengageStamina();
}

Simulated Function StartJog()
{
	ApplySpeedMultiplier(1.4);
}

Simulated function Walk()
{
    ApplySpeedMultiplier(1.5);
}

/** Stamina Functions and controls, go here for all stamina stuff, set Stamina manually and I will kill you! */

/** Returns true if stamina is usable **/
function bool hasStamina()
{
	if (self.isInState('Finishing') ) {
		return false;
	}
	else {

		return (Stamina > 0 && !bForceFullCooldown) || CurrentHealthState == EHS_Bad;
	}
}

function float getStaminaTax()
{
	//If moving (running) TODO: Base this on keys pressed rather than pawn's actual movement
	if( Physics == PHYS_Walking && Velocity.X > 0 || Velocity.Y > 0)
		return RUNNING_STAMINA_TAX;
	else return IDLE_STAMINA_TAX;
		
}

function engageStamina()
{
	SetBaseSpeed(DefaultRunSpeed); 
	ApplySpeedMultiplier(2.5);
	damageModifier = STAMINA_DAMAGE_MODIFIER;
	momentumModifier = STAMINA_MOMENTUM_MODIFIER;
	SetTimer(0.1, true, 'DecrementStamina');
	startHeartbeats();
	bUsingStamina = true;
}

function disengageStamina()
{	
	damageModifier = 1;
	momentumModifier = 1;
	ClearTimer('DecrementStamina');
    StartRegenStamina();
	bUsingStamina = false;
	ResetSpeed();
	CheckHealthState();
}

function DecrementStamina()
{
	if ( currentState == 'Attacking') {
		if (Normal(Controller.Pawn.Velocity) dot Normal(vector(Controller.Rotation)) < 0.5){
			ApplySpeedMultiplier(1.5);
		}
		else {
			ApplySpeedMultiplier(3.0);
		}
	}
	else {
		ApplySpeedMultiplier(3.0);
	}
    DecreaseStamina(getStaminaTax());
	updateHeartrate();
}

function DecreaseStamina(int Amount)
{
    if(Stamina > 0 /*&& !bForceFullCooldown*/)
    {
        Stamina -= Amount;
        Stamina = Max(Stamina, 0);
    }
    else
    {
        Stamina = 0;
    }
    if(Stamina <= 0)
    {
        ForceStaminaCoolDown();
    }
}

/*Code related to stamina feedback -- playing the heartbeat sound effect and breathing */

function updateHeartrate()
{
	local int idealRate; 
	idealRate = (Stamina == 0) ? 4 : 4 - ( FCeil(Stamina / 20)  );	
	if(heartrate != idealRate)
		setHeartrate(idealRate);
}

/**newHeartrate must be number between 0 and 4 **/
function setHeartrate(int newHeartrate)
{
	heartrate = newHeartrate;
	timeBetweenFullBeats = MAX_TIME_BETWEEN_FULL_HEARTBEATS - heartrate * ( MAX_TIME_BETWEEN_FULL_HEARTBEATS/6 );
	timeBetweenSingleBeats = MAX_TIME_BETWEEN_SINGLE_HEARTBEATS - heartrate * ( MAX_TIME_BETWEEN_SINGLE_HEARTBEATS/10 );
}

function startHeartbeats()
{
	SetTimer(timeBetweenSingleBeats,false, 'playHeartbeatOne');
}

function stopHeartbeats()
{
	ClearTimer('playHeartbeatOne');
	ClearTimer('playHeartbeatTwo');
}

//Play the first part of the heartbeat
function playHeartbeatOne()
{
	
	SetTimer(timeBetweenFullBeats, false, 'playHeartbeatTwo');
}

//Play the second part of the heartbeat
function playHeartbeatTwo()
{
	PlaySound(Heartbeat2);
	SetTimer(timeBetweenSingleBeats,false, 'playHeartbeatOne');
}

function startPanting()
{
	PantingSound.Play();
}

function stopPanting()
{
	PantingSound.Stop();
}

function updatePanting()
{
	PantingSound.VolumeMultiplier =  5 - ( Stamina / (StaminaMax/StaminaCooldownCeiling) ) * 5;
}

/**Interface for weapons. Decreases stamina by weapon determined amount if stamina is in use **/
function WeaponConsumeStamina(int Amount)
{
    if(bUsingStamina)
    {
		DecreaseStamina(Amount);
		//WorldInfo.Game.Broadcast(none, "Stamina decreased by: "@Amount);
    }
}

function StartRegenStamina() //Called to start regerating stamina
{
    EndReginStamina();
    SetTimer(3.0, false, 'BeginRecoverStamina');
}

function BeginRecoverStamina()
{
    RecoverStamina();
    if(Stamina < StaminaMax)
    {
        SetTimer(0.2, true, 'RecoverStamina');
    }
}

function EndReginStamina()
{
    clearTimer('RecoverStamina');
    clearTimer('BeginRecoverStamina');
}

function RecoverStamina()//Called to Increment stamina while running
{
    if(Stamina < StaminaMax)
    {
        Stamina +=1;
		updateHeartrate();
    }
    else
    {
        EndReginStamina();
		stopHeartbeats();
    }
    if(Stamina >= StaminaMax/StaminaCooldownCeiling)
    {
        if(bForceFullCooldown)
        {
            endForcedStaminaCooldown();
        }
    }
}

/**Release the stamina use lock**/
function endForcedStaminaCooldown()
{
	stopPanting();
	bForceFullCooldown = false;
}

function ForceStaminaCoolDown()//function to handle when we run out of stamina
{
	bForceFullCooldown = true;//Lock stamina use
	disengageStamina();
	startPanting();
}

/*-------------------------- Aiming---------------------------- */
function goToFirstPersonMode()
{
    if(canAim())
    {
        goToState('Aiming');
    }
}

simulated function bool canAim()
{
    return true;
}

State Aiming
{
    event BeginState(Name PreviousStateName)
    {
        //ApplySpeedMultiplier(0);
        bJumpCapable = false;
        PlayerController(Controller).SetCameraMode('FirstPerson');
        PlayerController(Controller).IgnoreMoveInput(true);
        ChangeVisibility(false);
    }
    
    simulated function bool canAim()
    {
        return False;
    }
    
    function goToFirstPersonMode()
    {
        goToState('Default');
    }
    
    simulated function name GetDefaultCameraMode(PlayerController RequestedBy)
    {
        return 'FirstPerson';
    }
    
    event EndState(Name NextStateName)
    {
        //ResetSpeed();
        bJumpCapable = true;
        PlayerController(Controller).IgnoreMoveInput(false);
        ChangeVisibility(true);
    }
}

simulated function StartFire(byte FireModeNum)
{
    if(canShove())
    {
        Shove();
    }
}

/*-------------------------- Shoving ---------------------------- */
function bool canShove()
{
    return true;
}

simulated function doShove()
{
}

function FinishShove()
{
}

function Shove()
{
    goToState('Shoving');
}

state Shoving
{
    event BeginState(Name PreviousStateName)
    {
        changestate(2, 0.2);
        ShoveNode.PlayAnim(false,1.0,0);
        bJumpCapable = false;
        PlayerController(Controller).IgnoreMoveInput(true);
        doShove();
		currentState = 'Shoving';
    }
    
    simulated function doShove()
    {
        local vector OtherLoc;
        local vector loc;
        local Pawn P;
        ForEach OverlappingActors(class'Pawn', P, ShoveRange, location)
        {
            if(P != self)//IGNORE YOURSELF!!!
            {
                Otherloc = P.Location;
                OtherLoc.z = 0;
                loc = location;
                loc.z = 0;
                if((OtherLoc dot Loc) > 0.75)
                {
                    P.TakeDamage(ShoveDamage*damageModifier, Controller, Location, Vector(Rotation)*ShoveMomentum*momentumModifier, class'DmgType_Crushed');
                    BlackDeathPawn(P).Stagger();
                }
            }
        }
		WeaponConsumeStamina(SHOVING_STAMINA_TAX);
    }
    
    function FinishShove()
    {
        goToState('Default');
    }
    
    event EndState(Name NextStateName)
    {
        ResetSpeed();
        PlayerController(Controller).IgnoreMoveInput(false);
        bJumpCapable = true;
        changestate(0, 0.2);
    }
}

simulated function bool Finisher()
{
    return false;
}

simulated function ToggleWalk()
{
    ApplySpeedMultiplier(0.5);
}

simulated function ToggleRun()
{
    ResetSpeed();
}

function ToggleAttacking()
{
    if(CanBeginAttackState())
    {
        goToState('Attacking');
    }
}

//If we can switch to attacking right now
function bool CanBeginAttackState()
{
	if( InvManager.InventoryChain != None )
	{	//Allow the player to switch to weapons torch/sword/etc.
		return true;
	}
	else
	{	//Do NOT allow the player to use weapons; the player may only use the shoving attack.
		return false;
	}
}

function CallAttack()
{
	//Once call attack has been called, there should be no movement during the animation.
	BlackDeathPlayerController(Controller).StopForwardMovement();
	super.CallAttack();
}

/*-------------------------- Attacking---------------------------- */
state Attacking
{
    event BeginState(Name PreviousStateName)
    {
        `log("entering attack ");
		
        if(!FinisherUsed)//finishers already have a weapon
		{	//prepare a weapon when starting attackig at all other times
			PrepareWeapon(PreviousStateName);
		}
		FinisherUsed = false;
        changeState(1, 0.2);
		currentState = 'Attacking';
    }
    
    simulated function bool Finisher()
    {
		local bool FIRST;
		local vector newDestination;
		local vector diff;
		local vector finisherLocation;
		local float radius;
        if( bNoWeaponFiring )
        {
            return false;
        }
		else {
			//context sensitive, snaps on to nearest downed enemy
			FIRST = true;
			newDestination = self.Location;
			radius = 55;
			ForEach OverLappingActors(class'BlackDeathPawn', Zombie, 55){
				if (Zombie.isinState('incapped')){
					Zombie.mesh.GetSocketWorldLocationAndRotation('Blood_Socket_01', finisherLocation);
					
					diff = self.Location - Zombie.Location;
					if (FIRST){
						snapOnDistance = diff;
						newDestination = self.Location;
						newDestination.x = finisherLocation.x + cos(150)*radius;
						newDestination.y = finisherLocation.y + sin(150)*radius;
						FIRST = false;
					}
					else if ( vsize(snapOnDistance) >= vsize(diff) ) {
						snapOnDistance = diff;
						newDestination = self.Location;
						newDestination.x = finisherLocation.x + cos(150)*radius;
						newDestination.y = finisherLocation.y + sin(150)*radius;
					}
				}
			}
			
			if (newDestination != self.Location){
				
				SetLocation(newDestination);
				newDestination = Normal(finisherLocation-self.Location);
				self.controller.SetRotation(Rotator(newDestination));
				PlayerController(Controller).SetCameraMode('FollowCam');
				BlackDeathCamera(PlayerController(Controller).PlayerCamera).SetRotation(Rotator(newDestination));
				BlackDeathCamera(PlayerController(Controller).PlayerCamera).ToggleRunning();  //test, to see animation from farther angle
				gotostate('Finishing');
				return true;
			}
			else {
				return false;
			}
		}
    }
    
    simulated function StartFire(byte FireModeNum)
    {
		if(IsTimerActive('comboOpportunity')  && bUsingStamina )
		{
			ClearTimer('comboOpportunity');
			bEarnedCombo = true;
		}

        if( bNoWeaponFiring || bWeaponFiring || bRecoil )
        {
            return;
        }

        if( Weapon != None )
        {
			bWeaponFiring = true;
            Weapon.StartFire(FireModeNum);
        }
    }
    
    function AnimAttachWeaponToHand()
    {
        Super.AnimAttachWeaponToHand();
        if(PlayerController(Controller).PlayerCamera.CameraStyle != 'ThirdPerson')
        {
            //PlayerController(Controller).SetCameraMode('ThirdPerson');
        }
    }
    
    function ToggleAttacking()
    {
        bEndAttackingState = true;
        RemoveWeapon();
    }
    
    simulated function bool canAim()
    {
        return False;
    }
    
    function bool canBeStaggered()
    {
        return false;
    }
    
    function bool canShove()
    {
        return false;
    }
    
    function FinishUnequip()
    {
        Super.FinishUnequip();
        if(bEndAttackingState)
        {
            goToState('Default');
        }
    }
    
    function PrepareWeapon(Name previousState)
    {
		if(LastHeldWeapon == None)
		{
			InvManager.SwitchToBestWeapon();
		}
		else
		{
			InvManager.SetCurrentWeapon(LastHeldWeapon);
		}
    }
    
    function RemoveWeapon()
    {
        Weapon.ForceEndFire();
        LastHeldWeapon = BlackDeathWeapon(Weapon);
        InvManager.SetCurrentWeapon(None);
    }
    
    event EndState(Name NextStateName)
    {
        bEndAttackingState = false;
    }
}

function bool CanChangeWeapon()
{
	return !bWeaponFiring;
}

simulated function NotifyAttacking()
{
    //ApplySpeedMultiplier(0.1);
}

function notifyAttackHit(BlackDeathMelee currentWeapon)
{
	//damageModifier += COMBO_DAMAGE_MODIFIER;
	SetTimer(COMBO_WINDOW, false, 'comboOpportunity');
}

simulated function NotifyEndAttaking()
{
	ClearTimer('comboOpportunity'); //No combo opportunities past the end of the attack
}

function comboOpportunity()
{
	//WorldInfo.Game.Broadcast(none, "Combo window closed");
	//Will check if timer is active to determine whether user input can trigger combo
}

/**If the player pressed the attack button on time, execute a combo, otherwise, begin the recoil. Return true if combo**/
function bool DetermineDoCombo()
{
	damageModifier = (bUsingStamina) ? STAMINA_DAMAGE_MODIFIER :  default.damageModifier;
	bWeaponFiring = false;
	if(bEarnedCombo)
	{
		bEarnedCombo = false;
		if(comboMoveToExecute == 0)
		{
			comboMoveToExecute = 1;
			BlackDeathPlayerController(Controller).StartForwardMovement(1000); //move forwared during the first part of the second combo
			damageModifier += COMBO_DAMAGE_MODIFIER;
			StartFire(0);
			return true;
		}
		else if(comboMoveToExecute == 1)
		{
			comboMoveToExecute = 2;
			BlackDeathPlayerController(Controller).StartForwardMovement(1000);
			damageModifier += COMBO_DAMAGE_MODIFIER+0.5; //TODO: make a proper const variable for this
			StartFire(0);
			return true;
		}
		else //earned another hit, but no more combos are allowed
		{
			StartRecoil();
		}
	}
	else
	{
		StartRecoil();
	}
	return false;
}

function StartRecoil()
{
	bRecoil = true;
	comboMoveToExecute = 0;
	SetTimer(BlackDeathWeapon(Weapon).recoil,false, 'EndRecoil');
}

function EndRecoil()
{
	bRecoil = false;
}

//This is the notify for holding a two handed weapon in the attack state.
simulated function NotifyPawnWeaponActive()
{
	if(!bWeaponFiring)
	{
		WeaponStateNode.SetActiveChild(2, 0.2);
		IdleStateNode.SetActiveChild(0, 0.2);
	}
	bWeaponFiring = false;
}


simulated function NotifyPawnTorchActive()
{
	if(!bWeaponFiring)
	{
		IdleStateNode.SetActiveChild( 2, 0.2);
		WeaponStateNode.SetActiveChild(2, 0.2);
	}
	bWeaponFiring = false;
}

/*-------------------------- Finishing---------------------------- */
state Finishing
{
    event BeginState(Name PreviousStateName)
    {
        `log("Moving to Finisher state");
        changestate(4, 0.2);
        //StateNode.PlayAnim(false,1.0,0.0);
        bJumpCapable = false;
        PlayerController(Controller).IgnoreMoveInput(true);
		currentState = 'Finishing';
		//PlayerController(Controller).IgnoreLookInput(true);
        //doFinisher();
    }
	
    simulated function doFinisher()
    {
		ForEach OverlappingActors(class'BlackDeathPawn', Zombie, 100){
			if (Zombie.isinState('incapped')){
				Zombie.TakeDamage(1, controller, location, vect(0,0,0), class'DmgType_Finish');
				break;
			}
		}
    }
    
    function FinshFinsher()
    {
		FinisherUsed = true;//flag to skip the prepareWeapon() call in the attacking state
        goToState('Attacking');
    }
    
    event EndState(Name NextStateName)
    {
        `log("Leaving finisher state");
		//PlayerController(Controller).IgnoreLookInput(false);
        PlayerController(Controller).IgnoreMoveInput(false);
        bJumpCapable = true;
		//PlayerController(Controller).SetCameraMode('ThirdPerson');
    }
    
    simulated function StartFire(byte FireModeNum)
    {

    }
    
    simulated function StopFire(byte FireModeNum)
    {

    }
    
    function bool canShove()
    {
        return false;
    }
}

function bool isAttackState(Name Statename)
{
    return Statename == 'Finishing';
}


//-----------------------Animation Functions----------------------//
function WeaponAnim()
{
    local BDMelee_GreatSword Weap;
    Weap = BDMelee_GreatSword(Weapon);
    if(Weap != none)
    {
        AnimNodeSequence(SkeletalMeshComponent(Weap.Mesh).FindAnimNode('Weapon_Finisher')).playAnim(false, 1.0, 0.0);
        //SkeletalMeshComponent(Weap.Mesh).playAnim('sword');
    }
}

simulated function PlayAnim( enum_animation animation )
{
	//plays animations based on an enumerated value  
	//that corisponds with the anim tree in the UDK content browser
	switch( animation ){
		case unequip_sword: case unequip_torch:
			WeaponStateNode.SetActiveChild( animation, 0.0 );
			WeaponStateNode.PlayAnim(false,1.0,0.0);
			
		break;
		default: 
			WeaponStateNode.SetActiveChild( animation, 0.0 );
			WeaponStateNode.PlayAnim(false,1.0,0.0);
			IdleStateNode.PlayAnim(true,0.9, 0.0);
			
		break;
	}
	
}

simulated function PlayWeaponSwing()
{
    `log(Self@": Playing Swing");

	if(comboMoveToExecute == 0)
	{
		Swing_Blend.SetBlendTarget(1,0.2);
		WeaponStateNode.SetActiveChild(1, 0.2);
		`log("combo is "@comboMoveToExecute); 
		HAttackType.SetActiveChild(comboMoveToExecute, 0.2);
		HAttackType.PlayAnim(false,1.0,0.0); 
	}
	else{
		//Apparently when you call "PlayAnim", it resets to the default pose or something before
		//starting the new anim. So when chaining combos we just change active child without "playing"
		HAttackType.SetActiveChild(comboMoveToExecute, 0.2);
	}
    
   //WeaponStateNode.PlayAnim(false,1.0,0.0);
}

simulated function EndSwing()
{
    local BlackDeathWeapon BDW;
    `log(Self@": Playing end swing");
    BDW = BlackDeathWeapon(weapon);
    if(BDW != none)
    {
        BDW.EndSwing();
    }
	if(!DetermineDoCombo())
	{ //If we aren't doing a combo, then return to the idle animation state.
		Swing_Blend.SetBlendTarget(0,0.2);
		WeaponStateNode.SetActiveChild(2, 0.2);
		IdleStateNode.PlayAnim(true,0.9, 0.0);
		//ResetSpeed();
	}
    
}

/*-------------------------- Staggered ----------------------------- */
State Staggered
{
    event BeginState(Name PreviousStateName)
    {
        lastStateBeforeStagger = PreviousStateName;
        ApplySpeedMultiplier(0.5);
        changeState(3, 0.2);
        setTimer(1.0, false, 'StaggerRecover');
		currentState = 'Staggered';
    }
    
    simulated function StaggerRecover()
    {
        goToState(lastStateBeforeStagger);
    }
    
    function bool canShove()
    {
        return false;
    }
    
    simulated function bool canAim()
    {
        return false;
    }
    
    function bool hasStamina()
    {
        return false;
    }
    
    event EndState(Name NextStateName)
    {
        ResetSpeed();
        changeState(0, 0.2);
        clearTimer('StaggerRecover');
    }
}

simulated function PlayDying(class<DamageType> DamageType, vector HitLoc)
{
  Super.PlayDying(DamageType, HitLoc);
  Health = 0;
  CheckHealthState();
  clearTimer('RecoverStamina');
  SetTimer(0.5, false, 'Respawn');
}

function Respawn()
{
}

/*-------------------------- Default Properties----------------------------- */
DefaultProperties
{
	Zombie = none;
	POTEnabled = false;
	LookAtCount = 0;
    InventoryManagerClass=class'BlackDeathHeroInventoryManager'
//Setting up the light environment
//Setting up the mesh and animset components
    Begin Object Name=InitialSkeletalMesh
        CastShadow=true
        bCastDynamicShadow=true
        bOwnerNoSee=false
        LightEnvironment=MyLightEnvironment;
        BlockRigidBody=true;
        CollideActors=true;
        BlockZeroExtent=true;
//What to change if you'd like to use your own meshes and animations
        PhysicsAsset=PhysicsAsset'BD_CHARACTERS.Mesh.Vincent_Mcgawain_Physics'
        AnimSets(0)=AnimSet'BD_CHARACTERS.Animation.VincentMoveSet'
		AnimSets(1)=AnimSet'BD_CHARACTERS.Vincent_Anim'
        AnimTreeTemplate=AnimTree'BD_Characters.Animation.Human_AnimTree'
        SkeletalMesh=SkeletalMesh'BD_CHARACTERS.Vincent_McGawain'
    End Object
//Setting up a proper collision cylinder
    Mesh=InitialSkeletalMesh;
    Components.Add(InitialSkeletalMesh); 
    CollisionType=COLLIDE_BlockAll
    Begin Object Name=CollisionCylinder
        CollisionRadius=+0024.000000
        CollisionHeight=+0044.000000
    End Object
    CylinderComponent=CollisionCylinder
	
	//Setting up Attachments for SkeletalMesh
 Begin Object Class=SkeletalMeshComponent Name=MySkeletalMeshComponent_01 
    CastShadow=true
    bCastDynamicShadow=true
    bOwnerNoSee=false
    LightEnvironment=MyLightEnvironment;
    SkeletalMesh=SkeletalMesh'BD_CHARACTERS.NEW_STACHEL_MESH'
  End Object
 Satchel=MySkeletalMeshComponent_01
    
    BurnLength = 5.0
    
    TiltDamageScale = 1.5;
	TiltDamagePeriod = 1;
	CameraDamage = 0;
	CHMultiplier = 5;
	CameraDamageSpeed = 5;
    HealthMax = 100
    CurrentHealthState = EHS_Excellent
    LocalHealthMax = 100
    ExcellentMinHealth = 67
    FairMinHealth = 34
	numberOfBandages = 0; //default number of bandages

    StaminaMax = 200
	StaminaCooldownCeiling = 2
    bForceFullCooldown = false
	bUsingStamina = false

	IDLE_STAMINA_TAX = 1
	RUNNING_STAMINA_TAX = 2
	SHOVING_STAMINA_TAX = 5
	STAMINA_DAMAGE_MODIFIER = 1.5
	STAMINA_MOMENTUM_MODIFIER = 1.5

	MAX_TIME_BETWEEN_FULL_HEARTBEATS = 0.6
	MAX_TIME_BETWEEN_SINGLE_HEARTBEATS = 0.25
	
	BandageCue = SoundCue'BD_SFX.bandageRip1_Cue'
	
	Heartbeat1 = SoundCue'BD_SFX.Heartbeat01'
	Heartbeat2 = SoundCue'BD_SFX.Heartbeat02'
	PANTING_SOUND_CUE = SoundCue'BD_SFX.Character.Panting'
	timeBetweenFullBeats = 0.6
	timeBetweenSingleBeats = 0.25   

	damageModifier = 1
    momentumModifier = 1

	COMBO_DAMAGE_MODIFIER = 1.5 //same as regular stamina for now
    COMBO_WINDOW = 1.0
	comboMoveToExecute = 0
	bEarnedCombo = false

	bWeaponFiring = false
	bRecoil = false

    UseableCheckRadius = 75
    BaseEyeHeight = 36
    
    DefaultRunSpeed = 75.000000
    
    bEndAttackingState=false
    
    ShoveRange = 100
    ShoveDamage = 5
    ShoveMomentum = 50
    
    AirSpeed=150.000000
    GroundSpeed=80.000000
    bCanCrouch=true
    bCanClimbLadders=True
    bCanPickupInventory=True
    bCanBeDamaged=true
    bCanSwim=true
	
	FinisherUsed = false;
    
    //InitialInventory(0)=class'BDMelee_Torch'
    //InitialInventory(1)=class'BDMelee_GreatSword'
}